Interactions

Games

  • Actions
    • Participants are given an action space and define their strategies
      • Tipically a categorical or continuous variable
  • Matching
    • Participants are matched into groups
    • Important to define the matching protocol
      • See module 2
  • Payoffs
    • Individual payoffs are computed at the group level
      • models.pyclass Group

Sequential an simultaneous games

  • Sequential games
    • Choices of one player are conditional upon choices of another player
  • Simultaneous games
    • Choices of players are independent from each other
  • In both cases choices are combined to define payoffs
  • In terms of programming, the two settings differ in the management of page sequences
    • We analyse here one sequential and one simultaneous game
      • Sequential: (mini) Trust game (TG)
      • Simultaneous: Public Goods Game (PGG)

A (mini) Trust Game (TG)

Setting

  • Players are matched in couples
    • A player is the first mover (Subject A)
    • A player is the second mover (Subject B)
  • Subject A can choose between enter the game (Option 2) or exit the game (Option 1)
    • If enter → Subject B can choose between reward (Option 4) or exploit (Option 3)
    • If exit → Subject B has no choice to marker
  • Payoffs are defined by the combination of choices by Subject A and B
    • (Option 1,.): 10, 0
    • (Option 2, Option 3): 0, 40
    • (Option 2, Option 4): 22, 18

oTree code: models.py

Constants


class Constants(BaseConstants):
    name_in_url = 'trust_game_corso'
    players_per_group = 2
    num_rounds = 1
    p_1=[10,0]
    p_2_3=[0,40]
    p_2_4=[22,18]

Subsession

class Subsession(BaseSubsession):
    def creating_session(self):
        for g in self.get_groups():
            for p in g.get_players():
                if p.id_in_group  == 1:
                    first = True
                else:
                    first = False
                p.first_mover = first

Group


class Group(BaseGroup):
    choice_1 = models.CharField(choices=[['Option 1','Option 1'], ['Option 2', 'Option 2']], widget=widgets.RadioSelectHorizontal, label="")
    # choice of Subject A
    choice_2 = models.CharField(choices=[['Option 3','Option 3'], ['Option 4', 'Option 4']], widget=widgets.RadioSelectHorizontal, label="")
    # choice of Subject B

    def set_payoffs(self):
        p1 = self.get_player_by_id(1)
        p2 = self.get_player_by_id(2)
        if self.choice_1 == 'Option 1':
        # if Subject A chose 1
            p1.payoff = Constants.p_1[0]
            p2.payoff = Constants.p_1[1]
        else:
        # if Subject A chose 2
            if self.choice_2 == 'Option 3':
              # if Subject B chose 3           
                p1.payoff = Constants.p_2_3[0]
                p2.payoff = Constants.p_2_3[1]
            else:
              # if Subject B chose 4           
                p1.payoff = Constants.p_2_4[0]
                p2.payoff = Constants.p_2_4[1] 

Player

class Player(BasePlayer):
    first_mover =models.BooleanField()
  • Role of the player

oTree code: pages.py

page_sequence = [Instructions, Choice_1, SendWaitPage, Choice_2, ResultsWaitPage, Results]

Choice_1

    form_model = 'group'
    form_fields = ['choice_1']
  # to collect choice of Subject B
    def is_displayed(self):
        return self.player.first_mover == 1
  • Collect the choice of Subject A
    • Notice that the field “belongs” to Group
  • The page is displayed only if the Subject is a first mover
    • As defined in Subsession

SendWaitPage

class SendWaitPage(WaitPage):
    pass
  • A waiting page so that Subject B waits that Subject A makes her choice
    • Important to control the sequential structure of the game

Choice_2

class Choice_2(Page):
    form_model = 'group'
    form_fields = ['choice_2']
  # to collect choice of Subject B
    def vars_for_template(self):
        return {
            'choice_other': self.group.choice_1
        }
  # needed to display the choice of A


    def is_displayed(self):
        return self.player.first_mover == False and self.group.choice_1 == 'Option 2'
      # condition to display the page
  • Collect the choice of Subject B
  • vars_for_template to display the choice of A
  • The page is displayed only if two conditions are simultaneously met
    • Player is not First mover
    • First mover chose Option 2
      • Otherwise, Subject B has no choice to make

ResultsWaitPage

class ResultsWaitPage(WaitPage):
    def after_all_players_arrive(self):
        self.group.set_payoffs()
  • A waiting page so that Subject A waits that Subject B makes her choice
  • After all make their choices we can compute payoffs
    • set_payoffs() at the Group level

Results


class Results(Page):
    def vars_for_template(self):
        return {
            'first_mover':self.player.first_mover,
            'choice_1': self.group.choice_1,
            'choice_2': self.group.choice_2,
            'payoff': self.player.payoff
        }
  • Variables needed to give feedback
    • payoff captures the earnings in the game

oTree code: Templates

Instructions

Choice_1

Choice_2

Results

A Public Goods Game (PGG)

Setting

  • Participants can decide how much contribute to a “public” project out of their endowment
  • Participants are in a group of N subjects (usually 4)
    • What is contributed is multiplied by an efficiency factor $1/N<<1 $
    • What is not contributed is kept in a private account
  • Private incentives are to contribute nothing to the public account
    • But, contributions are efficient
      • \(\Rightarrow\) social dilemma

Treatments: framing

  • We are going to frame the game eiter as a Voluntary Contribution game (VC) or as a Common Pool game (CP)
  • Voluntary Contribution game
    • Participants explicitly decide how much of their endowment contribute to the public project
      • What is not contributed is kept in a private account

Parameters

  • The interaction is repeated 10x in a partner fashion
    • Total earnings are given by the sum of earnings in each stage
  • Groups of 4
    • Matched together for the entire experiment (partner)
  • The efficiency factor \(\alpha=.5\)
  • The initial endowment is E=100
    • The individual payoff function is
      • VC
    • \(\Pi_i=E-c_i+\alpha \sum_j^N c_j\)
      • where, \(j\) are the members of \(i\)’s group (\(i\) include)
      • \(\sum_j^N c_j\) is the size of the public project
      • CP
        • \(\Pi_i=c_i+ \alpha (N*E-\sum_j^N c_j)\)
  • Choices are in integer steps
    • \(c_i \in \{0, 100\}\)

Results from a previous classroom implementation

oTree code: models.py

Constants

  • Set here a few important constants
    • Players per group
    • Number of rounds
    • Individual endowment
    • Multiplier for the public project
      • \(\alpha=\frac{2=multiplier}{4=group~members}\)
class Constants(BaseConstants):
    name_in_url = 'PGG'
    players_per_group = 4
    num_rounds = 10

    endowment = c(100)
    multiplier = 2

models: matching

  • In class Subsession we define the matching
    • No need to define roles in PGG
class Subsession(BaseSubsession):
    def creating_session(self):
#***************************************************************
# Partner matching
#***************************************************************
        if self.round_number == 1: # this way we get a fixed matching across repetitions
            self.group_randomly()
        else:
            self.group_like_round(1)
#***************************************************************
# Copy treatment for each player, needed for data analysis
#! configurations are not saved in the data
#***************************************************************
        for p in self.get_players():# copy the treatment for each subject
            p.treatment=self.session.config['treatment']

Compute payoffs

  • In class Group we define the payoffs
    • Typical of strategic interaction, while in individual decision making they are defined in Player

class Group(BaseGroup):
    total_choices = models.CurrencyField() # for VC is contributions , for CP is withdrawals
    individual_share = models.CurrencyField() #the share from public project
    total_earnings = models.CurrencyField() # share from public project + private account
#***************************************************************
# Define the method to compute payoff
#***************************************************************
    def set_payoffs(self):
    # 1) compute earnings from the public project
        players = self.get_players()
        # retrieve players in the subsession
        choices = [p.choice for p in players]
        # store their choices in a list. For CP they are withdrawals, For VC they are contributions
        self.total_choices = sum(choices)
        # sum the total choices (gives the size of the public project)
        if self.session.config['treatment'] == "VC":
        # if treatment is VC
            self.total_earnings = self.total_choices * Constants.multiplier
            # size of the public project
            self.individual_share = (
                self.total_earnings / Constants.players_per_group
            )
            # the individual share from the public project
            for p in players:
                p.payoff =  Constants.endowment-p.choice + self.individual_share #
            # compute individual payoffs (private + public account)
        else: # treatment CP
        # if treatment is CP
            self.total_earnings = ((Constants.endowment*Constants.players_per_group)-self.total_choices) * Constants.multiplier
            # size of the public project
            self.individual_share = (
                self.total_earnings / Constants.players_per_group
            )
            for p in players:
                p.payoff =  p.choice + self.individual_share
            # compute individual payoffs (private + public account)
            # each player has already a payoff value 

Store data

  • “Store” data to retrieve them in later rounds
  [...]      
        #store relevant data
        for p in players:
            history_payoff = [] # collect all individual payoffs in a list
            history_contrib = []# collect all contributions to the public project in a list
        # append previous values in a list
            for i in p.in_all_rounds():
                history_payoff.append(i.payoff)
                history_contrib.append(i.group.total_earnings)
              p.participant.vars['history_payoff']=history_payoff
              p.participant.vars['history_contrib']=history_contrib
            # values in participant.vars[] "survive" throughout the app

Compute final payoff

  • Final payoff is the sum of payoffs in all rounds

[...]      

    #compute final payoff (as sum of all payoffs)
      if self.round_number == Constants.num_rounds:
          for p in players:
              p.payoff_final=sum(p.participant.vars['history_payoff'])
              #print(p.payoff_final)

Player’s variables

  • Variables for players

[...]      

class Player(BasePlayer):
    choice = models.CurrencyField(min=0,max=Constants.endowment)#
    # for VC is withdrawals, for CP is contributions
    treatment=models.CharField()
    # copy the treatment, mainly for data analysis
    payoff_final=models.CurrencyField()
    # to store final payoff

oTree code: pages.py

pages.py

  • We have 5 pages
  • page_sequence = [Instructions, Contribute, ResultsWaitPage, Results, FinalResults]
    • Instructions(Page)
      • Displayed only in round 1
    • Contribute(Page)
    • ResultsWaitPage(WaitPage)
      • WaitPage → oTree waits until all players in the group have arrived at that point in the sequence, and then all players are allowed to proceed
        • Important that we have all data before computing the payoffs!
    • Results(Page)
    • FinalResults(Page)
      • Displayed only in last round

page_sequence = [Instructions, Contribute, ResultsWaitPage, Results, FinalResults]

Intructions(Page)

class Instructions(Page):
    def is_displayed(self):
    # define this method to return True if the page should be show
        return self.round_number == 1
        # show the page only in round 1

    def vars_for_template(self):
    # to pass variables to the template
        return{
        'treatment': self.session.config['treatment'],# needed to create the right treatment in the intructions
        'common_proj': Constants.endowment*Constants.players_per_group
        }
        # dictionary with variables needed in the template

Contribute(Page)

  • Contribute
    • Opposite meaning in VC snd CP
      • In VC is the actual contribution to the public project
      • In CP is the amount withdrawn (not contributed)

class Contribute(Page):
    form_model = 'player'
    form_fields = ['choice']
    #the player will fill out the field choice and it will be saved in player model (see models)

    def vars_for_template(self):
        return{
        'round_number':self.round_number,
        'treatment': self.session.config['treatment'],
        'endowment': Constants.endowment
        }

ResultsWaitPage(WaitPage)

class ResultsWaitPage(WaitPage):
    #If you have a WaitPage in your sequence of pages, then oTree waits until all players in the group have arrived at that point in the sequence, and then all players are allowed to proceed.
    after_all_players_arrive = 'set_payoffs'
    # see method 'set_payoffs' in class Group in models.py
    # payoffs are computed here

Results(Page)

  • Display results
    • Compute the amount kept accordingly
    • Retrieve history to draw the graph

class Results(Page):

    def vars_for_template(self):
        history = self.participant.vars['history_contrib']
        history= safe_json(history)
        #this is needed to improve display of values

        if self.session.config['treatment']=="VC":
            amount_kept=Constants.endowment-self.player.choice
        else:
            amount_kept=self.player.choice
        # compute the amount kept according to the treatment

        return{
        'round_number':self.round_number,
        'treatment': self.session.config['treatment'],
        'history_contrib': history,
        'kept': amount_kept
        }
        # variables needed in this page

FinalResults(Page)

  • Final results
    • Cumlative payment

class FinalResults(Page):
    def is_displayed(self):
        return self.round_number == Constants.num_rounds
# only displayed if it is the last round

    def vars_for_template(self):
        return{
        'payoff_final':self.player.payoff_final 
        }

Templates

Instructions

  • VC
  • CP

Choices

  • VC
  • CP

Results: round feedback (left panel)

  • VC
  • CP

Results (ii): graph history (right panel)

  • VC
  • CP

Results (iii): graph history (right panel)

  • In subfolder static/PGG we insert
    • The JavaScript file hihcharts.js
      • Library needed to create the graph
    • The JavaScript file miografico.js
      • Contains function to draw the graph
  • In the template the graph is “inserted” with
    • id=graph should match $('#graph').highcharts({ ...
     <div class="col-sm-6">
         <div id="graph" style="width:100%; height:300px;"> </div>
     </div>

Final Results

  • VC
  • CP

Appendix

Assignment TG

  1. Easy

    • Change payoffs so that it is rational to enter the game
  2. Difficult

    • Change protocol: from play method to strategy method
      • Subject B chooses before knowing the choice of Subject A
        • Afterwards, payoffs are computed according to choices of A and B

Assignment PGG

  1. Easy

    • Change endowment from 100 to 10000
  2. Less easy

    • Final payment is not the sum of all periods, but a randomly chosen period
  3. Not so difficult

    • Change matching protocol: from partner to random stranger

OTree code

  • The TG oTree app of this lecture:
Download TG.zip


  • The PGG oTree app of this lecture:
Download PGG.zip

References